#include "deploy/m_memory_deploy.h"
#include "m_memory.h"
#include "m_mutex.h"
#include <string.h>

#define BLOCK_SIZE 4
#define BLOCK_COUNT (ALLOC_BASE_SIZE / BLOCK_SIZE)


typedef struct MMemoryObjTag
{
    MMutex lock_;  // 锁
    UInt32 memory_[BLOCK_COUNT];  // 内存区起始
    UInt32 *memory_end_ = &memory_[BLOCK_COUNT - 1];  // 内存区结束
    UInt32 *last_block_adr_ = NULL;  // 最近操作的内存地址
    Int32  uesd_block_cnt_ = 0;  // 已使用的内存块个数
} MMemoryObj;

static Bool       is_init_ = False;  // 是否初始化
static MMemoryObj m_memory_;

Int32 MMemoryInit(void)
{
    if (! is_init_)
    {
        UInt32 block_info;

        MMutexCreate(&m_memory_.lock_);

        block_info = BLOCK_COUNT - 2;

        memset(m_memory_.memory_, 0x00, sizeof(m_memory_.memory_));

        // 填充内存块信息
        *m_memory_.memory_ = block_info;
        *m_memory_.memory_end_ = block_info;

        m_memory_.last_block_adr_ = m_memory_.memory_;

        m_memory_.uesd_block_cnt_ = 2;

        is_init_ = True;
    }

    return kErrCodeSucc;
}

Int32 MMemoryFini(void)
{
    if (is_init_)
    {
        is_init_ = False;

        MMutexDestory(&m_memory_.lock_);
    }

    return kErrCodeSucc;
}

static void* do_alloc(UInt32 *start, UInt32 *end, UInt32 size)
{
    void *ret_adr = NULL;

    UInt32 *look_adr   = start;
    UInt32  block_need = size / BLOCK_SIZE + (size % BLOCK_SIZE > 0 ? 1 : 0);
    UInt32  block_size = 0;
    Bool    block_used = False;

    while (look_adr < end)  // 从之前返回的内存块找到尾
    {
        block_used = (*look_adr) >> 31;
        block_size = (*look_adr) & 0x7FFFFFFF;

        if (! block_used && block_size >= block_need)  // 找到了
        {
            UInt32 left_size = block_size - block_need;

            if (left_size > 2)  // 多出来的内存值得拆分
            {
                UInt32 new_block_size = left_size - 2;  // 新内存块的大小

                // 更新当前内存块的信息
                *look_adr = block_need | 0x80000000;
                *(look_adr + block_need + 1) = *look_adr;
                m_memory_.last_block_adr_ = look_adr;

                // 设置新内存块的信息
                look_adr += block_need + 2;  // 移到新的内存块
                *look_adr = new_block_size;
                *(look_adr + new_block_size + 1) = *look_adr;
                m_memory_.uesd_block_cnt_ += block_need + 2;
            }
            else  // 多出来的内存不值得拆分，一并分配出来
            {
                // 更新当前内存块的信息
                *look_adr = block_size | 0x80000000;
                *(look_adr + block_size + 1) = *look_adr;
                m_memory_.last_block_adr_ = look_adr;
                m_memory_.uesd_block_cnt_ += block_size;
            }

            ret_adr = m_memory_.last_block_adr_ + 1;
            break;
        }

        look_adr += block_size + 2;  // 前往下一个内存块
    }

    return ret_adr;
}

void* MMalloc(UInt32 size)
{
    void *ret_adr = NULL;

    if (is_init_ && size > 0)
    {
        if (MMutexEnter(&m_memory_.lock_))
        {
            // 先从上次返回的内存块开始找
            ret_adr = do_alloc(m_memory_.last_block_adr_, m_memory_.memory_end_, size);

            if (ret_adr == NULL)  // 没找到，再从头找到之前返回的内存块
            {
                ret_adr = do_alloc(m_memory_.memory_, m_memory_.last_block_adr_, size);
            }

            MMutexLeave(&m_memory_.lock_);
        }
    }

    return ret_adr;
}

void MFree(void *space)
{
    if (is_init_ && MMutexEnter(&m_memory_.lock_))
    {
        if ((UInt32 *)space > m_memory_.memory_ && (UInt32 *)space < m_memory_.memory_end_)
        {
            UInt32 *block_adr = (UInt32 *)space - 1;

            // 更新当前内存块的信息
            *block_adr &= 0x7FFFFFFF;
            *(block_adr + *block_adr + 1) = *block_adr;
            m_memory_.uesd_block_cnt_ -= *block_adr;

            // 尝试合并下个内存块
            UInt32 *next_adr = block_adr + *block_adr + 2;
            if (next_adr < m_memory_.memory_end_ && (*next_adr) >> 31 == 0)  // 下个内存块空闲，开始合并
            {
                *block_adr += *next_adr + 2;
                *(block_adr + *block_adr + 1) = *block_adr;
                *next_adr = 0;
                *(next_adr - 1) = 0;
                m_memory_.uesd_block_cnt_ -= 2;
            }

            m_memory_.last_block_adr_ = block_adr;

            // 尝试合并上个内存块
            UInt32 *prev_adr = block_adr - 1;
            if (prev_adr > m_memory_.memory_ && (*prev_adr) >> 31 == 0)  // 上个内存块空闲，开始合并
            {
                // 移到上个内存块的头部
                prev_adr -= (*prev_adr) + 1;

                // 更新内存块信息
                *prev_adr += *block_adr + 2;
                *(prev_adr + *prev_adr + 1) = *prev_adr;

                *block_adr = 0;
                *(block_adr - 1) = 0;

                m_memory_.last_block_adr_ = prev_adr;
                m_memory_.uesd_block_cnt_ -= 2;
            }
        }

        MMutexLeave(&m_memory_.lock_);
    }
}

Int32 MMemoryGetTotalSpace(Int32* total_size)
{
    if (total_size == NULL)
    {
        return kErrCodeParamsErr;
    }

    if (! is_init_)
    {
        return kErrCodeNotCreate;
    }

    *total_size = ALLOC_BASE_SIZE;

    return kErrCodeSucc;
}

Int32 MMemoryGetUsedSize(Int32* used_size)
{
    if (used_size == NULL)
    {
        return kErrCodeParamsErr;
    }

    if (! is_init_)
    {
        return kErrCodeNotCreate;
    }

    Int32 result = kErrCodeElse;

    if (MMutexEnter(&m_memory_.lock_))
    {
        *used_size = m_memory_.uesd_block_cnt_ * BLOCK_SIZE;
        result = kErrCodeSucc;

        MMutexLeave(&m_memory_.lock_);
    }
    else
    {
        result = kErrCodeGetLockErr;
    }

    return result;
}

Int32 MMemoryGetPercentage(UInt8* percentage)
{
    if (percentage == NULL)
    {
        return kErrCodeParamsErr;
    }

    if (! is_init_)
    {
        return kErrCodeNotCreate;
    }

    Int32 result = kErrCodeElse;

    if (MMutexEnter(&m_memory_.lock_))
    {
        *percentage = (m_memory_.uesd_block_cnt_ * BLOCK_SIZE * 100) / ALLOC_BASE_SIZE;
        result = kErrCodeSucc;

        MMutexLeave(&m_memory_.lock_);
    }
    else
    {
        result = kErrCodeGetLockErr;
    }

    return result;
}
